Fix behavior of useHotkey when returned ref is re-attached to a diferent element. #1117
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Motivation
This pull request fixes issue #1116.
A brief look at the code
First of all: A test!
I added a test that fails if the issue #1116 is not fixed. This allows for any other solution to be adopted, should you not like my solution.
An auxiliary type
I added a type alias for KeyboardEventHandler (of the native kind - as opposed to the React.KeyboardEventHandler). It helps make the code more concise in the hook.
The hook (useHotkeys)
The most important change here is that the returned Ref is a RefCallback. The rest of the changes are driven by this crucial step.
What the callback
The returned callback leverages refs as callbacks behavior documented here.
As stated in the next section, refCallbacks should be stable, hence the
useCallback
around it.Why so many (use)refs?
To make the returned refCallback as stable as possible a few steps had to be made. As having any sort of
refObject.current
among the dependencies of any hook is generally a bad practice (because it won't work as expected), we need a way to update the attached handler reliably. This is simply achieved by making the attached handler stable for the life of the component using this hook. This means we can swap the active code of the handler (handleKeyUpRef.current
) without using the DOM API and we can simply add and remove the listeners (stableHandleKeyUpRef.current
) based on simpler conditions or events.Other comments
The code could be further improved, by splitting the giant
useEffect
into smaller chunks and, honestly the dependencies could also be dropped with smart usage of refObjects. Updating the value of arefObject.currect
when the user of your hook provides a new callback is virtually free. And if they want to make it stable or provide a new instance every time is their call.If we manage to get this merged into your package in this form or a refined one, I'm willing to help you further improve this package, should you be interested. 😉